/**
 * Copyright 2021 nicos AG
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @author Simon Petrac <spetrac@nicos-ag.com>
 * @contributor Jörg Langkau <jlangkau@nicos-ag.com>
 * @license Apache-2.0
 */

const
    config          = require('./config.js'),
    util            = require('./util.js'),
    md_renderer     = require('./renderer.md.js'),
    tcs_md_renderer = require('./renderer.tcs-md.js'),
    tds_md_renderer = require('./renderer.tds-md.js');

util.asyncIIFE(async function Main() {

    console.time('done');

    // collect all files from the config, even if only folders are specified
    console.log('=> collect configured files for data and shapes');
    const [ttlDataFiles, ttlShapeFiles, ttlModelFiles] = await Promise.all([
        util.expandFoldersToFiles(config.ttlDataFiles, /(?!\.example).{8}\.ttl|^.{1,7}\.ttl$/),
        util.expandFoldersToFiles(config.ttlShapeFiles, /\.shapes\.ttl$/),
        util.expandFoldersToFiles(config.ttlModelFiles, /\.ttl$/)
    ]);

    // load the data and the shapes from disc into an rdf/js dataset
    console.log('=> read files for data and shapes as datasets from disc');
    const [data, shapes, model] = await Promise.all([
        util.loadDataFromTurtleFiles(ttlDataFiles),
        util.loadDataFromTurtleFiles(ttlShapeFiles),
        util.loadDataFromTurtleFiles(ttlModelFiles)
    ]);

    // generate a json graph and validate the data with the shapes
    console.log('=> generate json graph from data and validate data against shapes');
    const [graph, shaclReport] = await Promise.all([
        util.generateGraphFromData(data),
        util.validateDataAgainstShapes(data, shapes)
    ]);

    // if strict rejection is configured and there is a problem with the data, cancel further execution
    if (config.shaclStrictRejection) {
        const conforms = shaclReport.has(null, util.ns.sh('conforms'), util.literal('true', util.ns.xsd('boolean')));
        if (!conforms) throw new Error('The loaded data does not conform with the shacl shapes.');
    }

    // validate the graph with a scripted validation function
    console.log('=> validate data and graph with the scripted validation');
    const scriptReport = await validateDataGraph(data, graph);

    // if strict rejection is configured and extra validation detects a problem, cancel further execution
    if (config.scriptStrictRejection) {
        const conforms = !!scriptReport.conforms;
        if (!conforms) throw new Error('The loaded data graph does not conform with the scripted validation.');
    }

    // save all data files and reports to disc
    console.log('=> save files for the catalog and the reports to disc');
    await Promise.all([
        util.saveDataToTurtleFile(config.ttlOutputFile, data)
            .then(() => console.log(`-> saved ${config.ttlOutputFile}`)),
        util.saveDataToJsonLdFile(config.jsonOutputFile, data)
            .then(() => console.log(`-> saved ${config.jsonOutputFile}`)),
        util.saveDataToTurtleFile(config.shaclReportFile, shaclReport)
            .then(() => console.log(`-> saved ${config.shaclReportFile}`)),
        util.writeFile(config.scriptReportFile, JSON.stringify(scriptReport, null, 2))
            .then(() => console.log(`-> saved ${config.scriptReportFile}`)),
        util.saveDataToTurtleFile(config.ttlModelOutputFile, model)
            .then(() => console.log(`-> saved ${config.ttlModelOutputFile}`)),
        util.saveDataToJsonLdFile(config.jsonModelOutputFile, model)
            .then(() => console.log(`-> saved ${config.jsonModelOutputFile}`))
    ]);

    // create the render parameter for further renderings into custom formats
    const renderParam = Object.freeze({
        // REM: define new general render parameters here
        version: config.current_version,
        data:    data,
        graph:   graph
    });

    //region >> rendering TDS
    console.log('=> render the markdown outputs for TDS to disc');
    if (config.markdownTDS?.dop) {
        const
            specificationArray = graph.getNodesByType("ids3cm:ComponentTestDesignSpecification").map(util.nodeToId),
            questionArray      = [
                ...graph.getNodesByType('ids3cm:CheckListQuestion_CHECKBOX_MULTI'),
                ...graph.getNodesByType('ids3cm:CheckListQuestion_CHECKBOX_SINGLE'),
                ...graph.getNodesByType('ids3cm:CheckListQuestion_TEXT'),
                ...graph.getNodesByType('ids3cm:CheckListQuestion_COMMENT'),
                ...graph.getNodesByType('ids3cm:CheckListQuestion_MATRIX')
            ].map(util.nodeToId);

        // iterate over the configured rendering languages
        await util.asyncMap(Object.entries(config.renderLanguages), async function renderTDS([language, languageConfig]) {
            const
                templateFile = config.markdownTDS.inputTemplate.replace('{$FIR_language}', language),
                outputFolder = config.markdownTDS.outputFolder.replace('{$FIR_language}', language);

            // skip the current language for falsy dop
            if (!languageConfig?.dop) {
                console.log(`-> skipped ${templateFile} because of falsy dop for language ${language}`);
                return;
            }

            const
                template = await util.readFile(templateFile);

            // iterate over all test design specifications found
            await util.asyncMap(specificationArray, async function renderTDS_toMarkdown(specification) {

                const
                    outputFile   = util.joinPath(outputFolder, specification.split(':')[1] + '.md'),
                    renderConfig = {language, specification, questionArray};

                const
                    missingFIRs  = [],
                    FIR_matcher  = /{[$](FIR.+?)}/g,
                    FIR_replacer = (match, key) => {
                        if (typeof tds_md_renderer[key] === 'function') {
                            const renderedResult = tds_md_renderer[key]?.(renderParam, renderConfig);
                            if (!renderedResult) console.log(`!> FIR returned empty ${match} for TDS ${specification}`);
                            return renderedResult || '';
                        } else {
                            if (!missingFIRs.includes(key)) {
                                console.log(`!> FIR not found ${match}`);
                                missingFIRs.push(key);
                            }
                            return match;
                        }
                    },
                    // replace all FIR in the template for the output
                    output       = template.replace(FIR_matcher, FIR_replacer);

                if (missingFIRs.length > 0 && config.markdownSkipIfMissingFIR) {
                    console.log(`-> skipped ${outputFile} because of missing FIR`);
                    return;
                }

                // write file on successful rendering
                await util.writeFile(outputFile, output);
                console.log(`-> saved ${outputFile}`);

            }); // renderTDS_toMarkdown
        }); // renderTDS
    } else {
        console.log(`-> skipped all TDS because of falsy dop`);
    }
    //endregion >> rendering TDS

    //region >> rendering TCS
    console.log('=> render the markdown outputs for TCS to disc');
    if (config.markdownTCS?.dop) {
        const
            specificationArray = graph.getNodesByType("ids3cm:ComponentTestCaseSpecification").map(util.nodeToId);

        // iterate over the configured rendering languages
        await util.asyncMap(Object.entries(config.renderLanguages), async function renderTCS([language, languageConfig]) {
            const
                templateFile = config.markdownTCS.inputTemplate.replace('{$FIR_language}', language),
                outputFolder = config.markdownTCS.outputFolder.replace('{$FIR_language}', language);

            // skip the current language for falsy dop
            if (!languageConfig?.dop) {
                console.log(`-> skipped ${templateFile} because of falsy dop for language ${language}`);
                return;
            }

            const
                template = await util.readFile(templateFile);

            // iterate over all test case specifications found
            await util.asyncMap(specificationArray, async function renderTCS_toMarkdown(specification) {

                const
                    outputFile   = util.joinPath(outputFolder, specification.split(':')[1] + '.md'),
                    renderConfig = {language, specification};

                const
                    missingFIRs  = [],
                    FIR_matcher  = /{[$](FIR.+?)}/g,
                    FIR_replacer = (match, key) => {
                        if (typeof tcs_md_renderer[key] === 'function') {
                            const renderedResult = tcs_md_renderer[key]?.(renderParam, renderConfig);
                            if (!renderedResult) console.log(`!> FIR returned empty ${match} for TCS ${specification}`);
                            return renderedResult || '';
                        } else {
                            if (!missingFIRs.includes(key)) {
                                console.log(`!> FIR not found ${match}`);
                                missingFIRs.push(key);
                            }
                            return match;
                        }
                    },
                    // replace all FIR in the template for the output
                    output       = template.replace(FIR_matcher, FIR_replacer);

                if (missingFIRs.length > 0 && config.markdownSkipIfMissingFIR) {
                    console.log(`-> skipped ${outputFile} because of missing FIR`);
                    return;
                }

                // write file on successful rendering
                await util.writeFile(outputFile, output);
                console.log(`-> saved ${outputFile}`);

            }); // renderTCS_toMarkdown
        }); // renderTCS
    } else {
        console.log(`-> skipped all TCS because of falsy dop`);
    }
    //endregion >> rendering TCS

    // render all configured markdown files and save them to their output location
    console.log('=> render the markdown outputs and save them to disc');
    await util.asyncMap(config.markdownOutput, async function renderFile_toMarkdown(renderConfig) {
        const {templateFile, outputFile} = renderConfig;

        // skip the current file for falsy dop
        if (!renderConfig.dop) {
            console.log(`-> skipped ${outputFile} because of falsy dop`);
            return;
        }

        // skip the current file for falsy dop of the language
        if (!config.renderLanguages[renderConfig.language ?? '']?.dop) {
            console.log(`-> skipped ${outputFile} because of falsy dop for language ${renderConfig.language}`);
            return;
        }

        const
            template     = await util.readFile(templateFile),
            missingFIRs  = [],
            FIR_matcher  = /{[$](FIR.+?)}/g,
            FIR_replacer = (match, key) => {
                if (typeof md_renderer[key] === 'function') {
                    const renderedResult = md_renderer[key]?.(renderParam, renderConfig);
                    if (!renderedResult) console.log(`!> FIR returned empty ${match}`);
                    return renderedResult || '';
                } else {
                    console.log(`!> FIR not found ${match}`);
                    missingFIRs.push(key);
                    return match;
                }
            },
            // replace all FIR in the template for the output
            output       = template.replace(FIR_matcher, FIR_replacer);

        if (missingFIRs.length > 0 && config.markdownSkipIfMissingFIR) {
            console.log(`-> skipped ${outputFile} because of missing FIR`);
            return;
        }

        // write file on successful rendering
        await util.writeFile(outputFile, output);
        console.log(`-> saved ${outputFile}`);

    }); // renderFile_toMarkdown

    console.timeEnd('done');

}); // Main

/**
 * A scripted method that is able to validate aspects, which were not validated by shacl shapes.
 * @param {Dataset} data The dataset containing the triples from the turtle files.
 * @param {Map<string, {'@id': string}>} graph The json graph, containing the same triples as the data.
 * @returns {Promise<{conforms: boolean}>} An object with at least one property 'conforms' indicating the validity.
 */
async function validateDataGraph(data, graph) {
    // the basic structure of the output => add to this object throughout the validations
    const scriptReport = {
        conforms:  true,
        remarks:   [],
        conflicts: []
    };

    // IDEA: search for double-blanks, conforms remains true
    // IDEA: check for bad files : ttl-only :: .\criteria\*
    // IDEA: check for bad files : ttl-only :: .\specification\test\design\*
    // IDEA: check for bad files : ttl-only :: .\specification\test\case\*

    return scriptReport;
} // validateDataGraph
